/* File:	Play.c
 * Purpose:	demonstrate Apple CD SC control
 * Date:	17 June 1988
 * Comments:
 *			This code is written for LightSpeed C version 2.15
 *			Create a project with this source plus MacTraps.
 *			Requires some associated resources (in the file Play.r).
 *			With this program on your hard disk, you can click
 *			on a track on an Audio CD and the CD SC drive will
 *			start audio play, beginning at that track and continuing
 *			to the end of the disc.
 *
 * Revisions:
 *
 *			Converted to converted to mpw 3.0 8/29 mb
 *
 *	Build instructions:	
 *
 *		rez -c MBAU :RIncludes:Types.r Play.r  -o Play 	
 *		c Play.c -w							
 *		link Play.c.o -o Play -c aucd ¶
 *		   "{CLibraries}"CRuntime.o ¶
 *		   "{CLibraries}"CInterface.o ¶
 *		   "{Libraries}"Interface.o
 *		
 *
 */

#include <types.h> 		/* Nearly always required */
#include <quickdraw.h> 	/* To access the qd globals */
#include <fonts.h> 		/* Only for InitFonts() trap */
#include <events.h>		/* GetNextEvent(), ... */
#include <windows.h>	/* GetNewWindow(), ... */
#include <dialogs.h>	/* InitDialogs() and GetNewDialog() */
#include <menus.h> 		/* EnableItem(), DisableItem() */
#include <desk.h>		/* SystemTask(), SystemClick() */
#include <TextEdit.h>
#include <Resources.h>	/* GetResource() */
#include <OSUtils.h>	/* SysBeep() */
#include <ToolUtils.h>
#include <SegLoad.h>
#include <Strings.h>
#include <Devices.h>
#include <Files.h>
#include <Packages.h>

#include <Memory.h>
#include <OSEvents.h>

/* Some constants, from the CD SC Developer's Guide chapter 7 */
#define	TRACKADDR	2
#define	STEREO		9
#define	INPROGRESS	0

/* Some commands, from the CD SC Developer's Guide chapter 7 */
#define	APLAY	104
#define	ASTOP	106
#define ASTATUS	107

/* The resource ID I use for my error dialog */
#define errorID	128

/* prototype function declarations. */
void	Error(char *, short);
void	ExtractTrackNo(char *, short *);
OSErr	AStop(short, short);
OSErr	APlay(short, short);
OSErr	Play(char *, short);
short	GetDrvRefNum(short);
void	PlayOnStartup(short);
Boolean	AskAndPlay(void);
void	main(void);
pascal void Debugger(/*void*/) extern 0xA9FF;

/************************************************************************
 *
 *  Function:		Error
 *
 *  Purpose:		Report an error to the user.
 *
 *  Returns:		nothing
 *
 *  Side Effects:	none.
 *
 *  Description:	Takes a string and a result code.  Convert the result
 *					into a pascal string using NumToString() and then just
 *					do a switch to determine which friendly error message
 *					to put up on the screen.  ParamText() sets up the alert
 *					so that it has the correct information.  Use Alert() to 
 *					actually display the message to the user.
 *
 ************************************************************************/
void
Error(where, result)
char	*where;
short	result;
{
	DialogPtr	dPtr;
	short		itemhit;
	Str255		strResult;
	long		longResult;
	
	longResult = result;
	NumToString(longResult, strResult);
	
	switch (result)
	{
		case -56:
			ParamText(where, strResult, "\pSpecified drive number doesn't match any number in the drive queue.", "\p");
			break;
		case -51:
			ParamText(where, strResult, "\pReference number specifies nonexistent access path.", "\p");
			break;
		case -50:
			ParamText(where, strResult, "\pError in parameter list.", "\p");
			break;
		case -43:
			ParamText(where, strResult, "\pFile not found.", "\p");
			break;
		case -36:
			ParamText(where, strResult, "\pI/O error.", "\p");
			break;
		case -35:
			ParamText(where, strResult, "\pNo such volume.", "\p");
			break;
		case -21:
			ParamText(where, strResult, "\pBad unit number.", "\p");
			break;
		default:
			ParamText(where, strResult, "\p", "\p");
			break;
	}
	
	Alert(errorID, (ProcPtr) NULL);
}

			  
/************************************************************************
 *
 *  Function:		ExtractTrackNo
 *
 *  Purpose:		Extract track number in BCD from PString
 *
 *  Returns:		nothing
 *
 *  Side Effects:	*track gets a new value
 *
 *  Description:	Extract track number in BCD from PString "name".
 *					"name" is always of the form "Track XX", where XX
 *					ranges from " 1" (1 with a leading space) to "99"
 *					(You can't have more than 99 tracks on a compact disc.
 *					Bet you didn't know that, did you?  Right in the "Yellow
 *					Book" specification of compact disc encoding.)
 *
 ************************************************************************/
void
ExtractTrackNo(name, track)
char		*name;
short		*track;
{
	short	size;
	short	t;
		
	t = 0;
	size = *name;
	if (name[size-1] != ' ')
		t = (name[size-1] - '0') * 16;
	t += name[size] - '0';
	
	*track = t;	
}

/************************************************************************
 *
 *  Function:		AStop
 *
 *  Purpose:		stop playing an audio track
 *
 *  Returns:		OSErr.  Probably either
 *						noErr		everything's hunky-dory!
 *						paramErr	you messed up the call somehow.
 *
 *  Side Effects:	stops play.
 *
 *  Description:	The track that you pass in is the LAST track that you
 *					want to have play.  This means that if you wanted to
 *					play only one track, you'd pass the same value to
 *					this routine and APlay() [q.v.]
 *
 *					Note that this routine isn't called in this sample,
 *					but it's included for your enjoyment.
 *
 ************************************************************************/
OSErr
AStop(track, refNum)
short	track;
short	refNum;
{
	struct {
		short	addrFormat;
		long	address;
	}	csParam;
	
	csParam.addrFormat = TRACKADDR;
	csParam.address = track;
	return (Control(refNum, ASTOP, &csParam));
}

/************************************************************************
 *
 *  Function:		APlay
 *
 *  Purpose:		start playing an audio track
 *
 *  Returns:		OSErr.  Probably either
 *						noErr		everything's hunky-dory!
 *						paramErr	you messed up the call somehow.
 *
 *  Side Effects:	starts play.  By default, this will play until the
 *					end of the disc.
 *
 *  Description:	The track that you pass in is the first track that you
 *					want to have play.
 *
 ************************************************************************/
OSErr
APlay(track, refNum)
short	track;
short	refNum;
{
	struct {
		short	addrFormat;
		long	address;
		short	stopAddress;
		short	playMode;
	}	csParam;
	
	csParam.addrFormat = TRACKADDR;
	csParam.address = track;	/* must be in BCD */
	csParam.stopAddress = 0;
	csParam.playMode = STEREO;
	return (Control(refNum, APLAY, &csParam));
}


/************************************************************************
 *
 *  Function:		Play
 *
 *  Purpose:		play a track, given it's "filename"
 *
 *  Returns:		OSErr
 *						whatever is returned by APlay()
 *
 *  Side Effects:	starts play of the audio track.
 *
 *  Description:	The two parameters passed in are the file name (which
 *					will always be of the form "Track XX", XX varying from
 *					" 1" to "99") and the driver reference number. Extract
 *					the track number and use it to call APlay() [q.v.]
 *
 ************************************************************************/
OSErr
Play(name, driveNumber)
char 		*name;
short		driveNumber;
{
	short	trackno;
	OSErr	result;
	
	ExtractTrackNo(name, &trackno);
	return( APlay(trackno, driveNumber) );
}


/************************************************************************
 *
 *  Function:		GetDrvRefNum
 *
 *  Purpose:		Get the driver reference number
 *
 *  Returns:		short.  The driver reference number
 *
 *  Side Effects:	none.
 *
 *  Description:	PBHGetVInfo() will retrieve the driver reference
 *					number, given the vRefNum associated with a file.
 *					We acquired the vRefNum as something passed into
 *					the program, either by SFGetFile() or by 
 *					GetAppFile().
 *
 *					We'll use the driver reference number in all our
 *					control calls to the driver.
 *
 ************************************************************************/
short
GetDrvRefNum(vRefNum)
short	vRefNum;
{
	HParamBlockRec	io;
	
	io.volumeParam.ioCompletion = NULL;
	io.volumeParam.ioNamePtr = NULL;
	io.volumeParam.ioVRefNum = vRefNum;
	io.volumeParam.ioVolIndex = 0;
	PBHGetVInfo(&io, false);
	return io.volumeParam.ioVDRefNum;
}



/************************************************************************
 *
 *  Function:		PlayOnStartup
 *
 *  Purpose:		Play the files passed in to our application
 *
 *  Returns:		nothing
 *
 *  Side Effects:	plays some files.
 *
 *  Description:	Loop, using GetAppFiles() to get the next "file"
 *					(actually audio CD track) to play.  Continue until
 *					we run out of files to play or until we get some
 *					error while trying to play a track.
 *
 *					Exercise to the programmer:
 *					As this routine is currently written, it doesn't
 *					handle opening multiple tracks at once.  (e.g. the
 *					user selects five tracks and opens them all.)  Right
 *					now, you'll only hear the last track (each Play()
 *					command cancels the previous one.)
 *
 *					Add a routine to check the status of the CD SC 
 *					drive (using AStatus) so that you wait for a track
 *					to finish before starting the next track.
 *
 ************************************************************************/
void
PlayOnStartup(count)
short	count;
{
	short	i;
	AppFile	app;
	OSErr	result;

	for (i = 0; i++<count; )
	{
		GetAppFiles(i, &app);
		if (app.fType == 'trak')
		{
			result = Play((char *)app.fName, GetDrvRefNum(app.vRefNum));
			if (result != noErr)
				Error("\pPlay returned", result);
		}
	}
}


/************************************************************************
 *
 *  Function:		AskAndPlay
 *
 *  Purpose:		Prompt the user for a track and play it
 *
 *  Returns:		Boolean.  Either:
 *						true	the user wants us to continue
 *						false	the user wants us to exit, or an error
 *								occurred.
 *
 *  Side Effects:	may play a track.
 *
 *  Description:	Use SFGetFile() with a specific type ('trak') to get
 *					a list of just audio CD tracks.  If the user selects
 *					an audio CD track, play it.
 *
 ************************************************************************/
Boolean
AskAndPlay()
{
	SFReply	sf;
	short	refnum;
	static Point where = {100,100};
	static OSType sftl = 'trak';
	Boolean result;
	OSErr	errorCode;
	
	result = true;
	FlushEvents(everyEvent-diskMask,0);
	SFGetFile(where, "\pSelect Track to Play", (ProcPtr)NULL,
		1, &sftl, (ProcPtr)NULL, &sf);
	if (!sf.good)
		result = false;
	else
	{
		errorCode = Play((char *)sf.fName, GetDrvRefNum(sf.vRefNum));
		if (errorCode != noErr)
		{
			Error("\pPlay returned", result);
			result = false;
		}
	}
	return result;
}


/************************************************************************
 *
 *  Function:		main
 *
 *  Purpose:		The Master Cylinder.   This is where we start.
 *
 *  Returns:		nothing.
 *
 *  Side Effects:	does whatever you tell it.
 *
 *  Description:	Do the standard Macintosh initialization.  Following
 *					all the boilerplate, use CountAppFiles() to see how
 *					we were started.  We could be started one of three
 *					ways:
 *					1)  The user opened our application.  We will have 
 *						a count of zero.  Go ask the user for a track.
 *					2)	The user opened an audio CD track "file".  We
 *						will have a non-zero count.  Go play the track(s)
 *						that the user asked.
 *
 ************************************************************************/
void
main()
{
	short	message, count;
	short	refNum;
	
	MaxApplZone();
	MoreMasters();

	InitGraf(&qd.thePort);
	InitFonts();
	InitWindows();
	InitMenus();
	TEInit();
	InitDialogs(0);

	InitCursor();
	FlushEvents(everyEvent-diskMask,0);

	CountAppFiles(&message, &count);
	if (count)
		PlayOnStartup(count);
	else
		while (AskAndPlay())
			;				/* null loop */
}
